This is a capacity leak bug.
The expected exception is
java.util.NoSuchElementException: Timeout waiting for idle object.
More details about this bug are at
POOL-162 JIRA page.
org.apache.commons.pool.impl.GenericObjectPool:
t1 t2 public Object borrowObject() throws Exception {
...
1 12 _allocationQueue.add(latch);
13 allocate();
...
switch(whenExhaustedAction) {
...
case WHEN_EXHAUSTED_BLOCK:
try {
synchronized (latch) {
if(maxWait <= 0) {
2 14 latch.wait();
}
...
}
}
catch(InterruptedException e) {
Thread.currentThread().interrupt();
3 throw e;
}
if(maxWait > 0 && ((System.currentTimeMillis() - starttime) >= maxWait)) {
throw new NoSuchElementException("Timeout waiting for idle object");
}
...
}
private synchronized void allocate() {
if (isClosed()) return;
// First use any objects in the pool to clear the queue
for (;;) {
8 if (!_pool.isEmpty() && !_allocationQueue.isEmpty()) {
Latch latch = (Latch) _allocationQueue.removeFirst();
9 latch.setPair((ObjectTimestampPair) _pool.removeFirst());
10 _numInternalProcessing++;
synchronized (latch) {
11 latch.notify();
}
} else {
break;
}
}
// Second utilise any spare capacity to create new objects
}
public void returnObject(Object obj) throws Exception {
try {
4 addObjectToPool(obj, true);
} catch (Exception e) {
...
}
}
private void addObjectToPool(Object obj, boolean decrementNumActive) throws Exception {
...
if (_lifo) {
5 _pool.addFirst(new ObjectTimestampPair(obj));
} else {
_pool.addLast(new ObjectTimestampPair(obj));
}
if (decrementNumActive) {
6 _numActive--;
}
7 allocate();
...
}
Precondition: _maxActive is 1, and 1 object has been borrowed from pool. Pool is empty.
a) thread 1 borrows an object, and associated latch is added into _allocationQueue at 1.
b) thread 1 waits at 2, because pool is empty.
c) thread 1 is interrupted, it throws an exception at 3 and terminates.
d) thread 2 returns the borrowed object to pool. It executes 4 and 5. There is one idle object in pool now.
e) active number is decreased at 6, _numActive is 0 now.
f) thread 2 calls allocate() at 7 and comes to 8. _pool is not
empty due to step d), _allocationQueue is not empty due to step a), and
_allocationQueue is not cleared at step c).
g) the idle object in pool is associated with the thread 1's dead
latch at 9. This object could never be returned to pool again and this
is memory leak.
h) _numInternalProcessing is added to 1 at 10.
i) The execution of 11 has no effects, because the waiting thread is already interrupted and at step c).
j) thread 2 goes through 12 and 13 to borrow an object from pool.
But the only object in pool has been associated to the dead thread1 at
step g), so thread 2 will wait forever at 14 and no one can notify it.
This bug is reproduced under pool 1.5 and JDK 1.6.0_33.
Execute the following scripts to run the test to reproduce the bug (assume the location of the pool test project is pool_test_home).
Linux:
${pool_test_home}/scripts/162.sh
Windows:
%pool_test_home%\scripts\162.bat